//===----------------------------------------------------------------------===//
//
//                         BusTub
//
// buffer_pool_manager.cpp
//
// Identification: src/buffer/buffer_pool_manager.cpp
//
// Copyright (c) 2015-2021, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#include "buffer/buffer_pool_manager.h"

#include "common/exception.h"
#include "common/macros.h"
#include "storage/page/page_guard.h"

namespace bustub {

BufferPoolManager::BufferPoolManager(size_t pool_size, DiskManager *disk_manager, size_t replacer_k,
                                     LogManager *log_manager)
    : pool_size_(pool_size), disk_scheduler_(std::make_unique<DiskScheduler>(disk_manager)), log_manager_(log_manager) {
  // TODO(students): remove this line after you have implemented the buffer pool manager
  // throw NotImplementedException(
  //     "BufferPoolManager is not implemented yet. If you have finished implementing BPM, please remove the throw "
  //     "exception line in `buffer_pool_manager.cpp`.");

  // we allocate a consecutive memory space for the buffer pool
  pages_ = new Page[pool_size_];
  replacer_ = std::make_unique<LRUKReplacer>(pool_size, replacer_k);

  // Initially, every page is in the free list.
  for (size_t i = 0; i < pool_size_; ++i) {
    free_list_.emplace_back(static_cast<int>(i));
  }
}

BufferPoolManager::~BufferPoolManager() { delete[] pages_; }
/*
  NewPage思路：在buffer pool中创建新的page，返回page_id，如果没有空闲的page，返回nullptr

  1.如果free_list为空，就使用replacer_找到一个victim page，如果victim page是dirty的，就flush到disk。
  2.然后将victim page的内容替换为新的page，使用AllocatePage()分配一个page_id。
  3.将新的page插入到page_table_中，返回新的page_id。
  记得使用replacer.setEvictable()固定victim page直到调用UnpinPage()。创建新page后，使用replacer.accessHistory()更新访问历史。
*/
auto BufferPoolManager::NewPage(page_id_t *page_id) -> Page * { 
  std::lock_guard<std::mutex> guard(latch_);
  if (free_list_.empty()) {
    frame_id_t frame_id;
    if (!replacer_->Evict(&frame_id)) {
      return nullptr;
    }
    auto *page = pages_ + frame_id;
    if (page->IsDirty()) {
      // flush to disk
      std::promise<bool> promise;
      auto future = promise.get_future();
      disk_scheduler_->Schedule({/*is_write=*/true, page->GetData(), page->GetPageId(), std::move(promise)});
      future.wait();
    }
    page_table_.erase(page->GetPageId());
    page->WLatch();
    page->ResetMemory();
    page->page_id_=AllocatePage();
    page->pin_count_=1;
    page_table_[page->GetPageId()] = frame_id;
    *page_id = page->GetPageId();
    replacer_->SetEvictable(frame_id, false);
    replacer_->RecordAccess(frame_id);
    page->WUnlatch();
    return page;
  }
  frame_id_t frame_id = free_list_.front();
  free_list_.pop_front();
  auto *page = pages_ + frame_id;
  page->WLatch();
  page->ResetMemory();
  page->page_id_ = AllocatePage();
  page->pin_count_ = 1;
  page_table_[page->GetPageId()] = frame_id;
  *page_id = page->GetPageId();
  replacer_->SetEvictable(frame_id, false);
  replacer_->RecordAccess(frame_id);
  page->WUnlatch();
  return page;
  
}

/*
  FetchPage思路：在buffer pool中找到page_id对应的page，如果page_id不在buffer pool中，就从disk中读取page_id对应的page，
  如果所有的frame都被占用并且都为pin，返回nullptr。
  1.首先在page_table_中查找page_id，如果找到了，就返回对应的page。
  2.如果没有找到，先从free_list或者replacer_中找到一个空闲的frame。
  3.使用disk_scheduler_->Schedule()函数从disk中读取page_id对应的page，取代frame中的旧page。
  4.如果旧page是dirty的，就flush到disk。
  5.同样要使用replacer.setEvictable()固定victim page直到调用UnpinPage()。创建新page后，使用replacer.accessHistory()更新访问历史。

*/

auto BufferPoolManager::FetchPage(page_id_t page_id, [[maybe_unused]] AccessType access_type) -> Page * {
  std::lock_guard<std::mutex> guard(latch_);
  auto it = page_table_.find(page_id);
  if (it != page_table_.end()) {
    auto *page = pages_ + it->second;
    page->RLatch();
    replacer_->RecordAccess(it->second);
    page->pin_count_++;
    return page;
  }
  
  if (free_list_.empty()) {
    frame_id_t frame_id;
    if (!replacer_->Evict(&frame_id)) {
      return nullptr;
    }
    auto *page = pages_ + frame_id;
    if (page->IsDirty()) {
      // flush to disk
      std::promise<bool> promise;
      auto future = promise.get_future();
      disk_scheduler_->Schedule({/*is_write=*/true, page->GetData(), page->GetPageId(), std::move(promise)});
      future.wait();
    }
    std::promise<bool> promise2;
    auto future2 = promise2.get_future();
    disk_scheduler_->Schedule({/*is_write=*/false, page->GetData(), page_id, std::move(promise2)});
    future2.wait();
    page_table_.erase(page->GetPageId());
    page->WLatch();
    //page->ResetMemory();
    page->page_id_ = page_id;
    page->pin_count_++;
    page_table_[page->GetPageId()] = frame_id;
    replacer_->SetEvictable(frame_id, false);
    replacer_->RecordAccess(frame_id);
    page->WUnlatch();
    return page;
  }
  frame_id_t frame_id = free_list_.front();
  free_list_.pop_front();
  auto *page = pages_ + frame_id;
  std::promise<bool> promise2;
  auto future2 = promise2.get_future();
  disk_scheduler_->Schedule({/*is_write=*/false, page->GetData(), page_id, std::move(promise2)});
  future2.wait();
  page->WLatch();
  //page->ResetMemory();
  page->pin_count_++;
  page->page_id_ = page_id;
  page_table_[page->GetPageId()] = frame_id;
  replacer_->SetEvictable(frame_id, false);
  replacer_->RecordAccess(frame_id);
  page->WUnlatch();
  return page;
}

/*
  UnpinPage思路：解除目标page的pin，如果page_id不在buffer pool中或者pin count已经为0，返回false。
  1.减少page的pin count，如果pin count为0，frame可以被replacer替换。
  2.设置page的dirty flag，表示page是否被修改。
*/
auto BufferPoolManager::UnpinPage(page_id_t page_id, bool is_dirty, [[maybe_unused]] AccessType access_type) -> bool {
  std::lock_guard<std::mutex> guard(latch_);
  auto it = page_table_.find(page_id);
  if (it == page_table_.end()) {
    return false;
  }
  auto *page = pages_ + it->second;
  page->WLatch();
  if (page->GetPinCount() <= 0) {
    page->WUnlatch();
    return false;
  }
  page->pin_count_--;
  if (is_dirty) {
    page->is_dirty_ = true;
  }
  if (page->GetPinCount() == 0) {
    replacer_->SetEvictable(it->second, true);
  }
  page->WUnlatch();
  return true;
}
/*
  FlushPage思路：将目标page刷新到disk。
  1.使用DiskManager::WritePage()函数刷新page到disk，不管dirty flag。
  2.刷新后，取消dirty flag。

*/
auto BufferPoolManager::FlushPage(page_id_t page_id) -> bool { 
  std::lock_guard<std::mutex> guard(latch_);
  auto it = page_table_.find(page_id);
  if (it == page_table_.end()) {
    return false;
  }
  auto *page = pages_ + it->second;
  page->WLatch();
  std::promise<bool> promise;
  auto future = promise.get_future();
  disk_scheduler_->Schedule({/*is_write=*/true, page->GetData(), page->GetPageId(), std::move(promise)});
  future.wait();
  page->is_dirty_ = false;
  page->WUnlatch();
  return true;
 }

void BufferPoolManager::FlushAllPages() {
  std::lock_guard<std::mutex> guard(latch_);
  for (size_t i = 0; i < pool_size_; ++i) {
    auto *page = pages_ + i;
    page->WLatch();
    if (page->IsDirty()) {
      std::promise<bool> promise;
      auto future = promise.get_future();
      disk_scheduler_->Schedule({/*is_write=*/true, page->GetData(), page->GetPageId(), std::move(promise)});
      future.wait();
      page->is_dirty_ = false;
    }
    page->WUnlatch();
  }
}

auto BufferPoolManager::DeletePage(page_id_t page_id) -> bool { 
  std::lock_guard<std::mutex> guard(latch_);
  auto it = page_table_.find(page_id);
  if (it == page_table_.end()) {
    return true;
  }
  auto *page = pages_ + it->second;
  page->WLatch();
  if (page->GetPinCount() > 0) {
    page->WUnlatch();
    return false;
  }
  page_table_.erase(page_id);
  free_list_.push_back(it->second);
  page->ResetMemory();
  DeallocatePage(page_id);
  page->WUnlatch();
  return true;
 }

auto BufferPoolManager::AllocatePage() -> page_id_t { return next_page_id_++; }

auto BufferPoolManager::FetchPageBasic(page_id_t page_id) -> BasicPageGuard { return {this, nullptr}; }

auto BufferPoolManager::FetchPageRead(page_id_t page_id) -> ReadPageGuard { return {this, nullptr}; }

auto BufferPoolManager::FetchPageWrite(page_id_t page_id) -> WritePageGuard { return {this, nullptr}; }

auto BufferPoolManager::NewPageGuarded(page_id_t *page_id) -> BasicPageGuard { return {this, nullptr}; }

}  // namespace bustub
